//	--------------------------------------------------------------------
//	Copyright(C) 2006,2007 Zhao Yukun. All rights reserved.
//	This file is a part of the Xeres system.
//	Xeres 2007
//	Contact : xeres.engine@gmail.com
//	$(reservedInfo)
//	$(reservedDeclaration)
//	--------------------------------------------------------------------
///	@file	<OutlineConsole.cpp>
///	@path	~/src/appear/xwin/
///	@date	2007/12/24
///	@desc	A tree view that outlines nearly all accessible nodes in engine.

#define _FULLAPI
#include "config/config.h"

#include "engine/xcore/xeres_engine.h"
#include "engine/xcore/event_center.h"

#include "appear/xwin/OutlineConsole.h"

namespace xeres {

	// OutlineConsole
	OutlineConsole::OutlineConsole( void )
		: m_actived( false )
		, m_reset( false )
	{

	}

	// ~OutlineConsole
	OutlineConsole::~OutlineConsole( void )
	{

	}

	// Initialize
	bool OutlineConsole::Initialize( Engine& engine , RefWeak<IData> root )
	{
		if( m_rootEntry.IsValid() )
		{
			TRACE_ERROR( _S("OutlineConsole::Initialize: Console has been initialized.") );
			return false;
		}

		m_rootEntry = root;
		m_currentDir = root;
		m_currentPath = _S("");
		m_focusItem = _S("");
		m_focusIndex = -1;
		m_lastFocusIndex = -1;

		m_vertexLayout = engine.GetRenderer()->DeclareVertexLayout( vd::ScreenColor::decl() );
		m_quads.Initialize( engine , m_vertexLayout );

		const bool sel = ( engine.GetRenderer()->GetColorOrder() != COLOR_ORDER_ARGB );
		vd::ScreenColor board_vert[] = {
			{ 0 , 0 , 0.5f, 1.0f, d3d_select_color(0xaa333333,sel), },
			{ 0 , 0 , 0.5f, 1.0f, d3d_select_color(0xaa333333,sel), },
			{ 0 , 0 , 0.5f, 1.0f, d3d_select_color(0xaa333333,sel), },
			{ 0 , 0 , 0.5f, 1.0f, d3d_select_color(0xaa333333,sel), },
			//
			{ 0 , 0 , 0.5f, 1.0f, d3d_select_color(0x77ddaa33,sel), },
			{ 0 , 0 , 0.5f, 1.0f, d3d_select_color(0x77ddaa33,sel), },
			{ 0 , 0 , 0.5f, 1.0f, d3d_select_color(0x77ddaa33,sel), },
			{ 0 , 0 , 0.5f, 1.0f, d3d_select_color(0x77ddaa33,sel), },
		};
		m_quads.Copy( board_vert );

		return true;
	}

	// Finalize
	void OutlineConsole::Finalize( void )
	{
		m_rootEntry = NULL;
		m_currentDir = NULL;

		m_canvas = NULL;
		m_board = NULL;
		m_treeFont = NULL;

		m_quads.Finalize();
	}

	// PrepareConsoleResource
	void OutlineConsole::PrepareConsoleResource( RenderContext& rc )
	{
		// Calculate regions.
		Rect<int> board( 30 , 30 , m_rect.right() - 30 , m_rect.bottom() - 30 );

		if( m_canvas.IsNull() )
		{
			// Create all new elements.
			m_canvas = new CCanvas;
			m_canvas->Initialize( rc.GetRenderer() );

			m_treeFont = rc.GetRenderer()->CreateFont(
				_S("Courier New") , -16 , 0 , 400 , false , true , true );

			m_board = new CCanvasTextBoard( m_treeFont , &m_nodes );

			m_canvas->AddElement( m_board );
		}
		m_board->SetRect( board );

		m_quads[0].x = (float)m_rect.left();  m_quads[0].y = (float)m_rect.bottom();
		m_quads[1].x = (float)m_rect.left();  m_quads[1].y = (float)m_rect.top();
		m_quads[2].x = (float)m_rect.right(); m_quads[2].y = (float)m_rect.bottom();
		m_quads[3].x = (float)m_rect.right(); m_quads[3].y = (float)m_rect.top();
		rc.ProcessRasterCoord( &(m_quads[0]) , sizeof(vd::ScreenColor) , 4 );
		m_quads.Commit();
	}

	// UpdateFocusItem
	void OutlineConsole::UpdateFocusItem( RenderContext& rc )
	{
		m_lastFocusIndex = m_focusIndex;

		if( m_focusIndex < 0 || m_focusIndex >= (int)m_nodes.size() )
		{
			m_focusIndex = 0;
			return;
		}

		int top = m_nodes[m_focusIndex].top;
		int bottom = m_nodes[m_focusIndex].bottom;

		Rect<int> rect( m_rect.left() , top , m_rect.right() , bottom );

		m_quads[4].x = (float)rect.left();  m_quads[4].y = (float)rect.bottom();
		m_quads[5].x = (float)rect.left();  m_quads[5].y = (float)rect.top();
		m_quads[6].x = (float)rect.right(); m_quads[6].y = (float)rect.bottom();
		m_quads[7].x = (float)rect.right(); m_quads[7].y = (float)rect.top();
		rc.ProcessRasterCoord( &(m_quads[4]) , sizeof(vd::ScreenColor) , 4 );
		m_quads.Commit();
	}

	// Tick
	void OutlineConsole::Tick( Engine& engine )
	{
		// update current dir
		m_nodes.clear();

		// check

		if( m_currentPath.empty() )
		{
			m_currentDir = m_rootEntry;
		}
		else
		{
			m_currentDir = m_rootEntry->OpenData( m_currentPath );
		}
		if( m_currentDir.IsNull() )
		{
			// node does not exist any more, return to root dir
			m_currentDir = m_rootEntry;
			m_currentPath = _S("");
			m_focusItem = _S("");
			m_focusIndex = -1;
			m_lastFocusIndex = 0;
		}
		else
		{
			// update all items
			DataInfo info;
			m_currentDir->GetInfo( info );
			switch( info.m_type )
			{
			case DATA_NULL:
				// no data
				break;
			case DATA_BIN:
				// binary data cannot be displayed
				break;
			case DATA_VAR:
				// a single variable
				break;
			case DATA_NODE:
				// an INode
				break;
			case DATA_FILE:
				// a file
				break;
			case DATA_DIR:
				// a directory
				break;
			default:
				assert(false);
			}

			// update child info
			size_t count = m_currentDir->GetChildCount();
			m_info.resize( count );
			m_nodes.resize( count );
			for( size_t i = 0 ; i < count ; ++i )
			{
				m_info[i].m_type = DATA_NULL;
				m_nodes[i].line.clear();
				m_nodes[i].color = Color4(1,1);
				m_currentDir->GetChildInfo( i , m_info[i] , m_nodes[i].line );
			}
		}

		// fix focus
		if( m_focusIndex < -1 )
		{
			m_focusIndex = 0;
		}

		if( m_focusIndex >= (int)m_nodes.size() )
		{
			if( m_nodes.size() == 0 )
				m_focusIndex = -1;
			else
				m_focusIndex = (int)m_nodes.size() - 1;
		}

		m_focusItem = _S("");
		if( m_focusIndex >= 0 )
		{
			m_nodes[m_focusIndex].color = Color4(0,1);
			m_focusItem = m_nodes[m_focusIndex].line;
		}
	}

	// Render
	void OutlineConsole::Render( RenderContext& rc )
	{
		if( rc.ResX<int>() != m_rect.width() || rc.ResY<int>() != m_rect.height() )
		{
			m_rect.set( 0 , 0 , rc.ResX<int>() , rc.ResY<int>() );
			PrepareConsoleResource( rc );
			m_reset = true;
		}

		RefWeak<IShaderAdapter> adapter = m_vertexLayout->GetDefaultAdapter();
		RefWeak<IShader> shader = adapter->AdaptShader( rc );
		adapter->AssembleShader( rc );
		m_quads.Establish( rc );
		UINT pass = 0;
		do 
		{
			shader->Begin( pass , rc );
			if( m_focusIndex > -1 )
				m_quads.Draw( rc );
			else
				m_quads.Draw( rc , 0 , 1 );
			shader->End( pass , rc );
		}while( pass );

		m_canvas->Render( rc );

		if( m_lastFocusIndex != m_focusIndex || m_reset )
		{
			UpdateFocusItem( rc );
			m_reset = false;
		}
	}

	// OnKeyInput
	HandleInput OutlineConsole::OnKeyInput( const KeyInput& input )
	{
		if( input.IsReleased() )
		{
			switch( input.GetKey() )
			{
			case KeyInput::KEY_UPARROW:
				// move to next focus
				if( m_focusIndex > 0 )
					--m_focusIndex;
				else
					m_focusIndex = 0;
				return INPUT_HANDLED;
			case KeyInput::KEY_DOWNARROW:
				// move to previous focus
				++m_focusIndex;
				return INPUT_HANDLED;
			case KeyInput::KEY_BACKSPACE:
				if( !m_currentPath.empty() )
				{
					size_t pos = m_currentPath.find_last_of( '/' );
					if( pos != WString::npos )
					{
						WString oldFocus = m_currentPath.substr( pos + 1 );
						m_currentPath = m_currentPath.substr( 0 , pos );

						if( m_currentPath.empty() )
						{
							m_currentDir = m_rootEntry;
						}
						else
						{
							m_currentDir = m_rootEntry->OpenData( m_currentPath );
						}
						if( m_currentDir.IsNull() )
						{
							// node does not exist anymore
							return INPUT_HANDLED;
						}

						// get focus index
						Key key = Key::QueryKey( oldFocus );
						if( !key.IsValid() )
						{
							return INPUT_HANDLED;
						}

						size_t index = 0;
						if( ! m_currentDir->FindChildIndex( key , index ) )
						{
							return INPUT_HANDLED;
						}
						m_focusIndex = (int)index;
						m_focusItem = oldFocus;
					}
					else
					{
						m_currentPath = _S("");
					}
				}
				return INPUT_HANDLED;
			case KeyInput::KEY_RETURN:
				// enter child
				if( m_focusIndex >= 0 )
				{
					if( m_currentPath.empty() )
					{
						m_currentDir = m_rootEntry;
					}
					else
					{
						m_currentDir = m_rootEntry->OpenData( m_currentPath );
					}
					if( m_currentDir.IsNull() )
					{
						// node does not exist anymore
						return INPUT_HANDLED;
					}
					DataInfo info;
					WString name;
					if( !m_currentDir->GetChildInfo( m_focusIndex , info , name ) )
					{
						// child changes
						return INPUT_HANDLED;
					}
					if( name != m_focusItem )
					{
						// child changes
						return INPUT_HANDLED;
					}
					switch( info.m_type )
					{
					case DATA_NULL:
						// no data
						return INPUT_HANDLED;
					case DATA_BIN:
						// binary data cannot be displayed
						return INPUT_HANDLED;
					case DATA_VAR:
						// a single variable
						return INPUT_HANDLED;
					case DATA_FILE:
						// a file
						// a directory
						{
							RefPass<IData> child = m_currentDir->GetChild( m_focusIndex );
							if( child.IsValid() )
							{
								m_currentPath += _S("/") + m_focusItem;
								m_currentDir = child;
								m_focusIndex = -1;
								m_focusItem = _S("");
							}
						}
						break;
					case DATA_NODE:
						// an dynamic node
					case DATA_DIR:
						// a directory
						m_currentPath += _S("/") + m_focusItem;
						m_currentDir = m_currentDir->GetChild( m_focusIndex );
						m_focusIndex = -1;
						m_focusItem = _S("");
						break;
					}
				}
				return INPUT_HANDLED;
			} // switch key
		}
		return INPUT_HANDLED;
	}

	// OnMouseInput
	HandleInput OutlineConsole::OnMouseInput( const MouseInput& input )
	{
		return INPUT_UNHANDLED;
	}

	// OnActive
	void OutlineConsole::OnActive( bool is_active )
	{
		m_actived = is_active;

		if( m_actived )
		{
			TRACE_INFO( _S("Active outline console ... ") );
		}
		else
		{
			TRACE_INFO( _S("Deactive outline console ... ") );
		}
	}

} // namespace xeres
